package com.gitee.wsl.compose.ui.swiperefresh.sample.state

import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.unit.Velocity
import com.gitee.wsl.compose.ui.swiperefresh.state.ActionStatus
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import timber.log.Timber
import kotlin.math.absoluteValue


internal class RefreshNestedScrollConnection(
    val state: RefreshLayoutState,
    val coroutineScope: CoroutineScope,
    var refreshEnabled: Boolean,
    var loadMoreEnabled: Boolean
) : NestedScrollConnection {

    private val defaultMultiplier = 1f
    private var multiplier = defaultMultiplier

    override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
        return when {
            source != NestedScrollSource.Drag -> Offset.Zero
//            state.refreshingState.componentStatus.shouldRejectScroll|| state.loadingMoreState.componentStatus.shouldRejectScroll -> Offset.Zero
            else -> handlePreScroll(available)
        }
    }

    /**
     * cases to handle:
     * scroll up when refreshing
     * scroll down when loading more
     */
    private fun handlePreScroll(available: Offset): Offset {
        val scrollUpWhenRefreshing = available.y < 0 && state.indicatorOffset > 0 && refreshEnabled
        val scrollDownWhenLoading = available.y > 0 && state.indicatorOffset < 0 && loadMoreEnabled
        val needHandle = scrollUpWhenRefreshing || scrollDownWhenLoading
        Timber.d("handleScroll[Pre]: available = $available,needHandle=$needHandle")
        if (!needHandle) {
            return Offset.Zero
        }
        val y = available.y
        var newOffsetY = state.indicatorOffset + y
        newOffsetY = if (state.indicatorOffset > 0) {
            //in refreshing process
            newOffsetY.coerceAtLeast(0f)
        } else {
            //in loading process
            newOffsetY.coerceAtMost(0f)
        }
        val consumedY = newOffsetY - state.indicatorOffset
        dispatchScroll(consumedY)
        return Offset(0f, consumedY)
    }


    override fun onPostScroll(
        consumed: Offset,
        available: Offset,
        source: NestedScrollSource
    ): Offset {
        Timber.d(
            "onPostScroll:available = $available,refreshingState=${state.refreshingState.componentStatus},loadingMoreState=${state.loadingMoreState.componentStatus} "
        )
        return when {
            source != NestedScrollSource.Drag -> Offset.Zero
            available.y > 0 && state.refreshingState.componentStatus.shouldRejectScroll -> Offset.Zero
            available.y < 0 && state.loadingMoreState.componentStatus.shouldRejectScroll -> Offset.Zero
            else -> handlePostScroll(available)
        }

    }

    /**
     * cases to handle:
     * scroll down when refreshing
     * scroll up when loading more
     */
    private fun handlePostScroll(available: Offset): Offset {
        val canRefresh = available.y > 0 && refreshEnabled
        val canLoadMore = available.y < 0 && loadMoreEnabled
        val needHandle = canRefresh || canLoadMore
        Timber.d("handleScroll[Post]: available = $available,needHandle=$needHandle")
        if (!needHandle) {
            return Offset.Zero
        }
        val y = available.y * multiplier
        val newOffsetY = state.indicatorOffset + y
        val delta = newOffsetY - state.indicatorOffset
        dispatchScroll(delta)
        updateMultiplier(if (available.y > 0) 1 else -1)
        return Offset(0f, available.y)
    }

    private fun updateMultiplier(dir: Int) {
        // 这里根据滑动距离衰减,可以优化一下这里的算法
        val max = if (dir > 0) state.maxScrollDownPx else state.maxScrollUpPx
        multiplier = (1f - state.indicatorOffset.absoluteValue / max).coerceAtLeast(0f)
    }

    override suspend fun onPreFling(available: Velocity): Velocity {
        Timber.d("onPreFling: available = $available")
        multiplier = defaultMultiplier
        return when {
            state.refreshingState.componentStatus == ActionStatus.ReadyForAction && state.refreshingState.hasMoreData -> {
                state.startRefresh()
                Velocity.Zero
            }

            state.loadingMoreState.componentStatus == ActionStatus.ReadyForAction && state.loadingMoreState.hasMoreData -> {
                state.startLoadMore()
                Velocity.Zero
            }

            state.refreshingState.componentStatus.shouldRejectScroll || state.loadingMoreState.componentStatus.shouldRejectScroll -> {
                available
            }

            else -> {
                state.idle()
                Velocity.Zero
            }
        }
    }

    private fun dispatchScroll(delta: Float) {
        coroutineScope.launch {
            state.dispatchScrollDelta(delta)
        }
    }
}

/**
 * 主要用于处理和协调Header或Footer与内容多个元素之间的滚动事件。
 *
 * @author <a href="mailto:jenly1314@gmail.com">Jenly</a>
 * <p>
 * <a href="https://github.com/jenly1314">Follow me</a>
 */
internal class UltraSwipeRefreshNestedScrollConnection(
    val state: RefreshLayoutState,
    private val coroutineScope: CoroutineScope,
    var refreshEnabled: Boolean,
    var loadMoreEnabled: Boolean
) : NestedScrollConnection {

    var dragMultiplier = 0.5f
    //var refreshEnabled: Boolean = false
    //var loadMoreEnabled: Boolean = false

    override fun onPreScroll(
        available: Offset,
        source: NestedScrollSource
    ): Offset = when {
        // 当下拉刷新和上拉加载都未启用时，则不进行消费，直接拦截
        !(refreshEnabled || loadMoreEnabled) -> Offset.Zero
        // 当正在刷新或正在加载或处理正在完成时，交由[onPostScroll]去处理
        state.isRefreshing || state.isLoading || state.isFinishing -> available
        // 当都Header和Footer都未显示时，则不进行消费，直接拦截
        state.indicatorOffset == 0f -> Offset.Zero
        // 当正在拖动时，则进行滚动处理
        source == NestedScrollSource.Drag -> onScroll(available)
        else -> Offset.Zero
    }

    override fun onPostScroll(
        consumed: Offset,
        available: Offset,
        source: NestedScrollSource
    ): Offset = when {
        // 当下拉刷新和上拉加载都未启用时，则不进行消费，直接拦截
        !(refreshEnabled || loadMoreEnabled) -> Offset.Zero
        // 当正在刷新或正在加载或处理正在完成时，则不进行消费，直接拦截
        state.isRefreshing || state.isLoading || state.isFinishing -> Offset.Zero
        // 当正在拖动时，则进行滚动处理
        source == NestedScrollSource.Drag -> onScroll(available)
        else -> Offset.Zero
    }

    private fun onScroll(available: Offset): Offset {
        if (available.y != 0f) {
            if (state.indicatorOffset <= 0f && available.y < 0f && !loadMoreEnabled) {
                return Offset.Zero
            }
            if (state.indicatorOffset >= 0f && available.y > 0f && !refreshEnabled) {
                return Offset.Zero
            }
            //state.isSwipeInProgress = true
            val dragConsumed = available.y * dragMultiplier
            coroutineScope.launch {
                state.dispatchScrollDelta(dragConsumed)
            }
            return available.copy(x = 0f)
        }
        return Offset.Zero
    }

    override suspend fun onPreFling(available: Velocity): Velocity {
       /* if (state.isRefreshing || state.isLoading || state.isFinishing) {
            state.isSwipeInProgress = false
            return available
        }
        when {
            refreshEnabled && state.isExceededRefreshTrigger() -> onRefresh()
            loadMoreEnabled && state.isExceededLoadMoreTrigger() -> onLoadMore()
            state.indicatorOffset != 0f && !state.isSwipeInProgress -> state.animateOffsetTo(0f)
        }
        state.isSwipeInProgress = false
        return Velocity.Zero*/

        Timber.d("onPreFling: available = $available")
        //multiplier = defaultMultiplier
        if (state.isRefreshing || state.isLoading) {
            return available
        }

       when {
            state.refreshingState.componentStatus == ActionStatus.ReadyForAction && state.refreshingState.hasMoreData ->
                state.startRefresh()

            state.loadingMoreState.componentStatus == ActionStatus.ReadyForAction && state.loadingMoreState.hasMoreData ->
                state.startLoadMore()
            /*state.refreshingState.componentStatus.shouldRejectScroll || state.loadingMoreState.componentStatus.shouldRejectScroll -> {
                available
            }*/
            else -> state.idle()

        }
        return Velocity.Zero
    }

    override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity {
        if (state.isRefreshing || state.isLoading) {
            return available
        }
        return Velocity.Zero
    }
}